using System.Linq;
using System.Collections.Generic;

namespace ClearSpringNet.Hash
{
    public class Lookup3Hash
    {
        /// <summary>
        /// A C# implementation of hashword from lookup3.c by Bob Jenkins
        /// </summary>
        /// <param name="k"></param>
        /// <param name="offset"></param>
        /// <param name="length"></param>
        /// <param name="initval"></param>
        /// <returns></returns>
        public static long Lookup3(int[] k, int offset, int length, int initval)
        {
            long a, b, c;
            a = b = c = 0xdeadbeef + (length << 2) + initval;

            int i = offset;
            while (length > 3)
            {
                a += k[i];
                b += k[i + 1];
                c += k[i + 2];

                // mix(a,b,c)... Java needs "out" parameters!!!
                // SmallNote : recent JVMs (Sun JDK6) turn pairs of shifts (needed to do a rotate)
                // into real x86 rotate instructions.
                {
                    a -= c; a ^= (c << 4) | ((uint)c >> -4); c += b;
                    b -= a; b ^= (a << 6) | ((uint)a >> -6); a += c;
                    c -= b; c ^= (b << 8) | ((uint)b >> -8); b += a;
                    a -= c; a ^= (c << 16) | ((uint)c >> -16); c += b;
                    b -= a; b ^= (a << 19) | ((uint)a >> -19); a += c;
                    c -= b; c ^= (b << 4) | ((uint)b >> -4); b += a;
                }

                length -= 3;
                i += 3;
            }

            switch (length)
            {
                case 3:
                case 2:
                case 1:
                    c += k[i + 2];  // fall through
                    b += k[i + 1];  // fall through
                    a += k[i + 0];  // fall through
                    // final(a,b,c);
                    {
                        c ^= b; c -= (b << 14) | (b >> -14);
                        a ^= c; a -= (c << 11) | (c >> -11);
                        b ^= a; b -= (a << 25) | (a >> -25);
                        c ^= b; c -= (b << 16) | (b >> -16);
                        a ^= c; a -= (c << 4) | (c >> -4);
                        b ^= a; b -= (a << 14) | (a >> -14);
                        c ^= b; c -= (b << 24) | (b >> -24);
                    }
                    break;
                case 0:
                    break;
                default:
                    break;
            }
            return c;
        }

        /// <summary>
        /// Identical to lookup3, except initval is biased by -(length&lt;&lt;2).
        /// This is equivalent to leaving out the length factor in the initial state.
        /// {@code lookup3ycs(k,offset,length,initval) == lookup3(k,offset,length,initval-(length
        /// bitwise left 2))}
        /// and {@code lookup3ycs(k,offset,length,initval+(length bitwise left 2)) == lookup3(k,offset,length,initval)}
        /// </summary>
        /// <param name="k"></param>
        /// <param name="offset"></param>
        /// <param name="length"></param>
        /// <param name="initval"></param>
        /// <returns></returns>
        public static long Lookup3Ycs(int[] k, int offset, int length, int initval)
        {
            return Lookup3(k, offset, length, initval - (length << 2));
        }

        /// <summary>
        /// The hash value of a character sequence is defined to be the hash of
        /// it's unicode code points, according to {@link #lookup3ycs(int[] k, int offset, int length, int initval)}
        /// If you know the number of code points in the {@code CharSequence}, you can
        /// generate the same hash as the original lookup3
        /// via {@code lookup3ycs(s, start, end, initval+(numCodePoints bitwise left 2))}
        /// </summary>
        /// <param name="s"></param>
        /// <param name="start"></param>
        /// <param name="end"></param>
        /// <param name="initval"></param>
        /// <returns></returns>
        public static long Lookup3Ycs(IEnumerable<char> s, int start, int end, int initval)
        {
            long a, b, c;
            a = b = c = 0xdeadbeef + initval;

            // only difference from lookup3 is that "+ (length<<2)" is missing
            // since we don't know the number of code points to start with,
            // and don't want to have to pre-scan the string to find out.

            int i = start;
            bool mixed = true;  // have the 3 state variables been adequately mixed?
            for (; ; )
            {
                if (i >= end) break;
                mixed = false;
                char ch;
                ch = s.ElementAt(i++);
                a += ch;
                if (i >= end) break;
                ch = s.ElementAt(i++);
                b += ch;
                if (i >= end) break;
                ch = s.ElementAt(i++);
                c += ch;
                if (i >= end) break;

                // mix(a,b,c)... Java needs "out" parameters!!!
                // SmallNote : recent JVMs (Sun JDK6) turn pairs of shifts (needed to do a rotate)
                // into real x86 rotate instructions.
                {
                    a -= c; a ^= (c << 4) | (c >> -4); c += b;
                    b -= a; b ^= (a << 6) | (a >> -6); a += c;
                    c -= b; c ^= (b << 8) | (b >> -8); b += a;
                    a -= c; a ^= (c << 16) | (c >> -16); c += b;
                    b -= a; b ^= (a << 19) | (a >> -19); a += c;
                    c -= b; c ^= (b << 4) | (b >> -4); b += a;
                }
                mixed = true;
            }

            if (!mixed)
            {
                // final(a,b,c)
                c ^= b; c -= (b << 14) | (b >> -14);
                a ^= c; a -= (c << 11) | (c >> -11);
                b ^= a; b -= (a << 25) | (a >> -25);
                c ^= b; c -= (b << 16) | (b >> -16);
                a ^= c; a -= (c << 4) | (c >> -4);
                b ^= a; b -= (a << 14) | (a >> -14);
                c ^= b; c -= (b << 24) | (b >> -24);
            }

            return c;
        }
        
        /// <summary>
        /// This is the 64 bit version of lookup3ycs, corresponding to Bob Jenkin's
        /// lookup3 hashlittle2 with initval biased by -(numCodePoints 2).  It is equivalent
        /// to lookup3ycs in that if the high bits of initval==0, then the low bits of the
        /// * result will be the same as lookup3ycs.
        /// </summary>
        /// <param name="s"></param>
        /// <param name="start"></param>
        /// <param name="end"></param>
        /// <param name="initval"></param>
        /// <returns></returns>
        public static long Lookup3Ycs64(IEnumerable<char> s, int start, int end, long initval)
        {
            long a, b, c;
            a = b = c = 0xdeadbeef + (int)initval;
            c += (int)(initval >> 32);

            // only difference from lookup3 is that "+ (length<<2)" is missing
            // since we don't know the number of code points to start with,
            // and don't want to have to pre-scan the string to find out.

            int i = start;
            bool mixed = true;  // have the 3 state variables been adequately mixed?
            for (; ; )
            {
                if (i >= end) break;
                mixed = false;
                char ch;
                ch = s.ElementAt(i++);
                a += ch;
                if (i >= end) break;
                ch = s.ElementAt(i++);
                b += ch;
                if (i >= end) break;
                ch = s.ElementAt(i++);
                c += ch;
                if (i >= end) break;

                // mix(a,b,c)... Java needs "out" parameters!!!
                // SmallNote: recent JVMs (Sun JDK6) turn pairs of shifts (needed to do a rotate)
                // into real x86 rotate instructions.
                {
                    a -= c; a ^= (c << 4) | (c >> -4); c += b;
                    b -= a; b ^= (a << 6) | (a >> -6); a += c;
                    c -= b; c ^= (b << 8) | (b >> -8); b += a;
                    a -= c; a ^= (c << 16) | (c >> -16); c += b;
                    b -= a; b ^= (a << 19) | (a >> -19); a += c;
                    c -= b; c ^= (b << 4) | (b >> -4); b += a;
                }
                mixed = true;
            }
            if (!mixed)
            {
                // final(a,b,c)
                c ^= b; c -= (b << 14) | (b >> -14);
                a ^= c; a -= (c << 11) | (c >> -11);
                b ^= a; b -= (a << 25) | (a >> -25);
                c ^= b; c -= (b << 16) | (b >> -16);
                a ^= c; a -= (c << 4) | (c >> -4);
                b ^= a; b -= (a << 14) | (a >> -14);
                c ^= b; c -= (b << 24) | (b >> -24);
            }

            return c + ((b) << 32);
        }

        public static long Lookup3Ycs64(IEnumerable<char> s)
        {
            return Lookup3Ycs64(s, 0, s.Count(), -1);
        }
    }
}